#ifndef _VULKAN_DEVICE_H_
#define _VULKAN_DEVICE_H_

namespace Vulkan {
	inline VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
		VkDebugReportFlagsEXT flags,
		VkDebugReportObjectTypeEXT objType,
		uint64_t obj,
		size_t location,
		int32_t code,
		const char* layerPrefix,
		const char* msg,
		void* userData) {
#if defined(WIN32)
		auto str = "VALIDATION LALYER : " + std::string(msg) + "\n";
		OutputDebugString(str.c_str());
#else
		std::cout << "VALIDATION LALYER : " << msg << std::endl;
#endif
		if (flags == VK_DEBUG_REPORT_ERROR_BIT_EXT || flags == VK_DEBUG_REPORT_WARNING_BIT_EXT) {
			throw;
		}
		return VK_FALSE;
	}

	namespace DeviceInit {
		void setupInstance() {
			VkApplicationInfo appInfo = {};
			appInfo.apiVersion = VK_API_VERSION_1_0;
			std::vector<const char*> extensions = {
				VK_KHR_SURFACE_EXTENSION_NAME
			};
#if defined(WIN32)
			extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(__linux__)
			extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#endif

#if defined(_DEBUG)
			extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
#endif // defined(_DEBUG)

			VkInstanceCreateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
			info.enabledExtensionCount = (uint32_t)extensions.size();
			info.ppEnabledExtensionNames = extensions.data();
			info.pApplicationInfo = &appInfo;
#if defined(_DEBUG)
			const char* validationLayer = "VK_LAYER_LUNARG_standard_validation";
			info.enabledLayerCount = 1;
			info.ppEnabledLayerNames = &validationLayer;
#else
			info.enabledLayerCount = 0;
#endif
			vkCreateInstance(&info, nullptr, &context.instance);
		}

		void setupDebug() {
			auto createFunc = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(context.instance, "vkCreateDebugReportCallbackEXT");
			VkDebugReportCallbackCreateInfoEXT info = {};
			info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
			info.pfnCallback = debugCallback;
			info.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;

			VkDebugReportCallbackEXT debug = VK_NULL_HANDLE;
			createFunc(context.instance, &info, nullptr, &context.debugCallback);
		}

		void destroyDebug(const VkInstance& instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
			auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
			if (func != nullptr) {
				func(instance, callback, pAllocator);
			}
		}

		void setupPhysicalDevice() {
			uint32_t count = 0;
			vkEnumeratePhysicalDevices(context.instance, &count, nullptr);
			std::vector<VkPhysicalDevice> devices(count);
			vkEnumeratePhysicalDevices(context.instance, &count, devices.data());
			context.physicalDevice = devices[0];
		}

		void setupDevice() {
			std::vector<const char*> extensions = {
				VK_KHR_SWAPCHAIN_EXTENSION_NAME
			};

			float priorities = 1.0f;
			VkDeviceQueueCreateInfo queueInfo = {};
			queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
			queueInfo.queueCount = 1;
			queueInfo.queueFamilyIndex = context.graphicsQueueIndex;
			queueInfo.pQueuePriorities = &priorities;

			VkDeviceCreateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
			info.enabledExtensionCount = (uint32_t)extensions.size();
			info.ppEnabledExtensionNames = extensions.data();
			info.queueCreateInfoCount = 1;
			info.pQueueCreateInfos = &queueInfo;
			info.enabledLayerCount = 0;

			vkCreateDevice(context.physicalDevice, &info, nullptr, &context.device);
		}

		void setupQueue() {
			vkGetDeviceQueue(context.device, context.graphicsQueueIndex, 0, &context.graphicsQueue);
		}

		void setupQueueFamily() {
			uint32_t count = 0;
			vkGetPhysicalDeviceQueueFamilyProperties(context.physicalDevice, &count, nullptr);
			std::vector<VkQueueFamilyProperties> properties(count);
			vkGetPhysicalDeviceQueueFamilyProperties(context.physicalDevice, &count, properties.data());
			for (size_t i = 0; i < properties.size(); i++) {
				if (properties[i].queueCount>0) {
					VkBool32 support = VK_FALSE;
					vkGetPhysicalDeviceSurfaceSupportKHR(context.physicalDevice, (uint32_t)i, context.surface, &support);
					if (support == VK_TRUE && properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
						context.graphicsQueueIndex = (uint32_t)i;
						return;
					}
				}
			}
			throw std::runtime_error("setupQueueFamily");
		}

		void setupComandPool() {
			VkCommandPoolCreateInfo cmdPoolInfo = {};
			cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
			cmdPoolInfo.queueFamilyIndex = context.graphicsQueueIndex;
			cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
			VkCommandPool pool = VK_NULL_HANDLE;
			vkCreateCommandPool(context.device, &cmdPoolInfo, nullptr, &context.commandPool);
		}

		VkFormat getSupportedDepthFormat()
		{
			// Since all depth formats may be optional, we need to find a suitable depth format to use
			// Start with the highest precision packed format
			std::vector<VkFormat> depthFormats = {
				VK_FORMAT_D32_SFLOAT_S8_UINT,
				VK_FORMAT_D32_SFLOAT,
				VK_FORMAT_D24_UNORM_S8_UINT,
				VK_FORMAT_D16_UNORM_S8_UINT,
				VK_FORMAT_D16_UNORM
			};

			for (auto& format : depthFormats)
			{
				VkFormatProperties formatProps;
				vkGetPhysicalDeviceFormatProperties(context.physicalDevice, format, &formatProps);
				// Format must support depth stencil attachment for optimal tiling
				if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
				{
					return format;
				}
			}

			throw std::runtime_error("can not find depth format");
		}

		inline VkSurfaceFormatKHR getSurfaceFormat() {
			VkSurfaceFormatKHR surfaceFormat;
			uint32_t formatCount = 0;
			vkGetPhysicalDeviceSurfaceFormatsKHR(context.physicalDevice, context.surface, &formatCount, nullptr);
			std::vector<VkSurfaceFormatKHR> formats(formatCount);
			vkGetPhysicalDeviceSurfaceFormatsKHR(context.physicalDevice, context.surface, &formatCount, formats.data());
			if ((formatCount == 1) && (formats[0].format == VK_FORMAT_UNDEFINED))
			{
				surfaceFormat.format = VK_FORMAT_B8G8R8A8_UNORM;
			}
			else
			{
				surfaceFormat.format = formats[0].format;
			}
			surfaceFormat.colorSpace = formats[0].colorSpace;
			return surfaceFormat;
		}

		inline VkPresentModeKHR getPresendMode() {
			VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
			uint32_t presentModeCount = 0;
			vkGetPhysicalDeviceSurfacePresentModesKHR(context.physicalDevice, context.surface, &presentModeCount, nullptr);
			std::vector<VkPresentModeKHR> presentModes(presentModeCount);
			vkGetPhysicalDeviceSurfacePresentModesKHR(context.physicalDevice, context.surface, &presentModeCount, presentModes.data());
			for (size_t i = 0; i < presentModeCount; i++)
			{
				if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
				{
					presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
					break;
				}
				if ((presentMode != VK_PRESENT_MODE_MAILBOX_KHR) && (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR))
				{
					presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
				}
			}
			return presentMode;
		}

		inline void setupSwapchain() {
			VkSurfaceCapabilitiesKHR surfCaps;
			vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.physicalDevice, context.surface, &surfCaps);
			uint32_t imageCount = surfCaps.minImageCount + 1;
			if (surfCaps.maxImageCount>0 && imageCount>surfCaps.maxImageCount) {
				imageCount = surfCaps.maxImageCount;
			}
			context.extend = surfCaps.currentExtent;
			VkSwapchainCreateInfoKHR info = {};
			info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
			info.surface = context.surface;
			info.preTransform = surfCaps.currentTransform;
			info.imageExtent = surfCaps.currentExtent;
			info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
			info.minImageCount = imageCount;
			info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
			info.imageArrayLayers = 1;
			info.presentMode = getPresendMode();
			info.imageFormat = context.colorFormat;
			info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
			info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

			vkCreateSwapchainKHR(context.device, &info, nullptr, &context.swapchain);
		}

		void setupDepthStencil()
		{
			VkImageCreateInfo info = {};
			info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
			info.imageType = VK_IMAGE_TYPE_2D;
			info.format = context.depth.format;
			info.extent = { context.extend.width, context.extend.height, 1 };
			info.mipLevels = 1;
			info.arrayLayers = 1;
			info.samples = VK_SAMPLE_COUNT_1_BIT;
			info.tiling = VK_IMAGE_TILING_OPTIMAL;
			info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
			info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
			vkCreateImage(context.device, &info, nullptr, &context.depth.image);
			context.depth.memory = createMemory(context.depth.image, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

			VkImageViewCreateInfo depthStencilView = {};
			depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
			depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
			depthStencilView.image = context.depth.image;
			depthStencilView.format = context.depth.format;
			depthStencilView.flags = 0;
			depthStencilView.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,0,1,0,1 };
			vkCreateImageView(context.device, &depthStencilView, nullptr, &context.depth.view);
		}

		void setupImageView() {
			uint32_t count = 0;
			vkGetSwapchainImagesKHR(context.device, context.swapchain, &count, nullptr);
			context.swapchainImages.resize(count, VK_NULL_HANDLE);
			vkGetSwapchainImagesKHR(context.device, context.swapchain, &count, context.swapchainImages.data());
			context.swapchainViews.resize(count, VK_NULL_HANDLE);

			for (uint32_t i = 0; i < count; i++)
			{
				VkImageViewCreateInfo info = {};
				info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
				info.format = context.colorFormat;
				info.image = context.swapchainImages[i];
				info.viewType = VK_IMAGE_VIEW_TYPE_2D;
				info.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY };
				info.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
				vkCreateImageView(context.device, &info, nullptr, &context.swapchainViews[i]);
			}
		}

		void setupFrameBuffer() {
			context.swapchainFrames.resize(context.swapchainViews.size());
			for (size_t i = 0; i < context.swapchainViews.size(); i++)
			{
				VkImageView attachments[2] = {
					context.swapchainViews[i],context.depth.view
				};
				VkFramebufferCreateInfo info = {};
				info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
				info.renderPass = context.renderPass;
				info.attachmentCount = 2;
				info.pAttachments = attachments;
				info.width = context.extend.width;
				info.height = context.extend.height;
				info.layers = 1;
				vkCreateFramebuffer(context.device, &info, nullptr, &context.swapchainFrames[i]);
			}
		}

		void init() {
			glfwInit();
			glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
			context.window = glfwCreateWindow(800, 600, "title", nullptr, nullptr);

			setupInstance();
			setupDebug();
			glfwCreateWindowSurface(context.instance, context.window, nullptr, &context.surface);
			setupPhysicalDevice();

			setupQueueFamily();
			setupDevice();
			setupQueue();
			setupComandPool();

			vkGetPhysicalDeviceMemoryProperties(context.physicalDevice, &context.memoryPropertes);
			context.colorFormat = getSurfaceFormat().format;
			context.depth.format = getSupportedDepthFormat();

			setupSwapchain();
			setupDepthStencil();
			setupImageView();

			context.renderPass = Pass::createBasePass();

			setupFrameBuffer();
		}
	}
}


#endif // !_VULKAN_DEVICE_H_

